//  MetricsUtil.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
package jmetal.qualityIndicator.util;

import jmetal.core.Solution;
import jmetal.core.SolutionSet;
import jmetal.qualityIndicator.Hypervolume;
import jmetal.util.NonDominatedSolutionList;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.*;

/**
 * This class provides some utilities to compute quality indicators. 
 *
 */
public class MetricsUtil {

    /**
     * This method reads a Pareto Front for a file.
     *
     * @param path The path to the file that contains the pareto front
     * @return double [][] whit the pareto front
   *
     */
    public double[][] readFront(String path) {
        try {
            // Open the file
            FileInputStream fis = new FileInputStream(path);
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);

            List<double[]> list = new ArrayList<double[]>();
            int numberOfObjectives = 0;
            String aux = br.readLine();
            while (aux != null) {
                StringTokenizer st = new StringTokenizer(aux);
                int i = 0;
                numberOfObjectives = st.countTokens();
                double[] vector = new double[st.countTokens()];
                while (st.hasMoreTokens()) {
                    double value = new Double(st.nextToken());
                    vector[i] = value;
                    i++;
                }
                list.add(vector);
                aux = br.readLine();
            }

            br.close();

            double[][] front = new double[list.size()][numberOfObjectives];
            for (int i = 0; i < list.size(); i++) {
                front[i] = list.get(i);
            }
            return front;

        } catch (Exception e) {
            System.out.println("InputFacilities crashed reading for file: " + path);
            e.printStackTrace();
        }
        return null;
    } // readFront

    /**
     * Gets the maximum values for each objectives in a given pareto front
     *
     * @param front The pareto front
     * @param noObjectives Number of objectives in the pareto front
     * @return double [] An array of noOjectives values whit the maximun values
     * for each objective
   *
     */
    public double[] getMaximumValues(double[][] front, int noObjectives) {
        double[] maximumValue = new double[noObjectives];
        for (int i = 0; i < noObjectives; i++) {
            maximumValue[i] = Double.NEGATIVE_INFINITY;
        }

        for (double[] aFront : front) {
            for (int j = 0; j < aFront.length; j++) {
                if (aFront[j] > maximumValue[j]) {
                    maximumValue[j] = aFront[j];
                }
            }
        }

        return maximumValue;
    } // getMaximumValues

    /**
     * Gets the minimum values for each objectives in a given pareto front
     *
     * @param front The pareto front
     * @param noObjectives Number of objectives in the pareto front
     * @return double [] An array of noOjectives values whit the minimum values
     * for each objective
   *
     */
    public double[] getMinimumValues(double[][] front, int noObjectives) {
        double[] minimumValue = new double[noObjectives];
        for (int i = 0; i < noObjectives; i++) {
            minimumValue[i] = Double.MAX_VALUE;
        }

        for (double[] aFront : front) {
            for (int j = 0; j < aFront.length; j++) {
                if (aFront[j] < minimumValue[j]) {
                    minimumValue[j] = aFront[j];
                }
            }
        }
        return minimumValue;
    } // getMinimumValues

    /**
     * This method returns the distance (taken the euclidean distance) between
     * two points given as <code>double []</code>
     *
     * @param a A point
     * @param b A point
     * @return The euclidean distance between the points
   *
     */
    public double distance(double[] a, double[] b) {
        double distance = 0.0;

        for (int i = 0; i < a.length; i++) {
            distance += Math.pow(a[i] - b[i], 2.0);
        }
        return Math.sqrt(distance);
    } // distance

    /**
     * Gets the distance between a point and the nearest one in a given front
     * (the front is given as <code>double [][]</code>)
     *
     * @param point The point
     * @param front The front that contains the other points to calculate the
     * distances
     * @return The minimun distance between the point and the front
   *
     */
    public double distanceToClosedPoint(double[] point, double[][] front) {
        double minDistance = distance(point, front[0]);

        for (int i = 1; i < front.length; i++) {
            double aux = distance(point, front[i]);
            if (aux < minDistance) {
                minDistance = aux;
            }
        }

        return minDistance;
    } // distanceToClosedPoint

    /**
     * Gets the distance between a point and the nearest one in a given front,
     * and this distance is greater than 0.0
     *
     * @param point The point
     * @param front The front that contains the other points to calculate the
     * distances
     * @return The minimun distances greater than zero between the point and the
     * front
     */
    public double distanceToNearestPoint(double[] point, double[][] front) {
        double minDistance = Double.MAX_VALUE;

        for (double[] aFront : front) {
            double aux = distance(point, aFront);
            if ((aux < minDistance) && (aux > 0.0)) {
                minDistance = aux;
            }
        }

        return minDistance;
    } // distanceToNearestPoint

    /**
     * This method receives a pareto front and two points, one whit maximum
     * values and the other with minimum values allowed, and returns a the
     * normalized Pareto front.
     *
     * @param front A pareto front.
     * @param maximumValue The maximum values allowed
     * @param minimumValue The minimum values allowed
     * @return the normalized pareto front
   *
     */
    public double[][] getNormalizedFront(double[][] front,
            double[] maximumValue,
            double[] minimumValue) {

        double[][] normalizedFront = new double[front.length][];

        for (int i = 0; i < front.length; i++) {
            normalizedFront[i] = new double[front[i].length];
            for (int j = 0; j < front[i].length; j++) {
                normalizedFront[i][j] = (front[i][j] - minimumValue[j]) / (maximumValue[j] - minimumValue[j]);
            }
        }
        return normalizedFront;
    } // getNormalizedFront

    /**
     * This method receives a normalized pareto front and return the inverted
     * one. This operation needed for minimization problems
     *
     * @param front The pareto front to inverse
     * @return The inverted pareto front
   *
     */
    public double[][] invertedFront(double[][] front) {
        double[][] invertedFront = new double[front.length][];

        for (int i = 0; i < front.length; i++) {
            invertedFront[i] = new double[front[i].length];
            for (int j = 0; j < front[i].length; j++) {
                if (front[i][j] <= 1.0 && front[i][j] >= 0.0) {
                    invertedFront[i][j] = 1.0 - front[i][j];
                } else if (front[i][j] > 1.0) {
                    invertedFront[i][j] = 0.0;
                } else if (front[i][j] < 0.0) {
                    invertedFront[i][j] = 1.0;
                }
            }
        }
        return invertedFront;
    } // invertedFront

    /**
     * Reads a set of non dominated solutions from a file
     *
     * @param path The path of the file containing the data
     * @return A solution set
     */
    public SolutionSet readSolutionSet(String path) {
        try {
            /* Open the file */
            FileInputStream fis = new FileInputStream(path);
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);

            SolutionSet solutionSet = new SolutionSet();

            String aux = br.readLine();
            while (aux != null) {
                StringTokenizer st = new StringTokenizer(aux);
                int i = 0;
                Solution solution = new Solution(st.countTokens());
                while (st.hasMoreTokens()) {
                    double value = new Double(st.nextToken());
                    solution.setObjective(i, value);
                    i++;
                }
                solutionSet.setCapacity(solutionSet.getCapacity() + 1);
                solutionSet.add(solution);
                aux = br.readLine();
            }
            br.close();
            return solutionSet;
        } catch (Exception e) {
            System.out.println("jmetal.qualityIndicator.util.readNonDominatedSolutionSet: " + path);
            e.printStackTrace();
        }
        return null;
    } // readSolutionSet

    /**
     * Reads a set of non dominated solutions from a file
     *
     * @param path The path of the file containing the data
     * @return A solution set
     */
    public SolutionSet readNonDominatedSolutionSet(String path) {
        try {
            /* Open the file */
            FileInputStream fis = new FileInputStream(path);
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);

            SolutionSet solutionSet = new NonDominatedSolutionList();

            String aux = br.readLine();
            while (aux != null) {
                StringTokenizer st = new StringTokenizer(aux);
                int i = 0;
                Solution solution = new Solution(st.countTokens());
                while (st.hasMoreTokens()) {
                    double value = new Double(st.nextToken());
                    solution.setObjective(i, value);
                    i++;
                }
                solutionSet.add(solution);
                aux = br.readLine();
            }
            br.close();
            return solutionSet;
        } catch (Exception e) {
            System.out.println("jmetal.qualityIndicator.util.readNonDominatedSolutionSet: " + path);
            e.printStackTrace();
        }
        return null;
    } // readNonDominatedSolutionSet

    /**
     * Reads a set of non dominated solutions from a file and store it in a
     * existing non dominated solution set
     *
     * @param path The path of the file containing the data
     * @return A solution set
     */
    public void readNonDominatedSolutionSet(String path, NonDominatedSolutionList solutionSet) {
        try {
            /* Open the file */
            FileInputStream fis = new FileInputStream(path);
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);

            String aux = br.readLine();
            while (aux != null) {
                StringTokenizer st = new StringTokenizer(aux);
                int i = 0;
                Solution solution = new Solution(st.countTokens());

                while (st.hasMoreTokens()) {
                    double value = new Double(st.nextToken());
                    solution.setObjective(i, value);
                    i++;
                }
                solutionSet.add(solution);
                aux = br.readLine();
            }
            br.close();
        } catch (Exception e) {
            System.out.println("jmetal.qualityIndicator.util.readNonDominatedSolutionSet: " + path);
            e.printStackTrace();
        }
    }

    /**
     * Calculates how much hypervolume each point dominates exclusively. The
     * points have to be transformed beforehand, to accommodate the assumptions
     * of Zitzler's hypervolume code.
     *
     * @param front transformed objective values
     * @return HV contributions
     */
    public double[] hvContributions(int numberOfobjectives, double[][] front) {
        Hypervolume hypervolume = new Hypervolume();
        int numberOfObjectives = numberOfobjectives;
        double[] contributions = new double[front.length];
        double[][] frontSubset = new double[front.length - 1][front[0].length];
        LinkedList<double[]> frontCopy = new LinkedList<double[]>();
        Collections.addAll(frontCopy, front);
        double[][] totalFront = frontCopy.toArray(frontSubset);
        double totalVolume = hypervolume.calculateHypervolume(totalFront, totalFront.length, numberOfObjectives);
        for (int i = 0; i < front.length; i++) {
            double[] evaluatedPoint = frontCopy.remove(i);
            frontSubset = frontCopy.toArray(frontSubset);
            // STEP4. The hypervolume (control is passed to java version of Zitzler code)
            double hv = hypervolume.calculateHypervolume(frontSubset, frontSubset.length, numberOfObjectives);
            double contribution = totalVolume - hv;
            contributions[i] = contribution;
            // put point back
            frontCopy.add(i, evaluatedPoint);
        }
        return contributions;
    }

    /**
     * Calculates the hv contribution of different populations. Receives an
     * array of populations and computes the contribution to HV of the
     * population consisting in the union of all of them
     *
     * @param populations, consisting in all the populatoins
     * @return HV contributions of each population
	   *
     */
    public double[] hvContributions(SolutionSet[] populations) {
        boolean empty = true;
        for (SolutionSet population2 : populations) {
            if (population2.size() > 0) {
                empty = false;
            }
        }

        if (empty) {
            double[] contributions = new double[populations.length];
            for (int i = 0; i < populations.length; i++) {
                contributions[i] = 0;
            }
            for (int i = 0; i < populations.length; i++) {
                System.out.println(contributions[i]);
            }
            return contributions;
        }

        SolutionSet union;
        int size = 0;
        double offset_ = 0.0;

        //determining the global size of the population
        for (SolutionSet population1 : populations) {
            size += population1.size();
        }

        //allocating space for the union 
        union = new SolutionSet(size);

        // filling union
        for (SolutionSet population : populations) {
            for (int j = 0; j < population.size(); j++) {
                union.add(population.get(j));
            }
        }

        //determining the number of objectives		  
        int numberOfObjectives = union.get(0).getNumberOfObjectives();

        //writing everything in matrices
        double[][][] frontValues = new double[populations.length + 1][][];

        frontValues[0] = union.writeObjectivesToMatrix();
        for (int i = 0; i < populations.length; i++) {
            if (populations[i].size() > 0) {
                frontValues[i + 1] = populations[i].writeObjectivesToMatrix();
            } else {
                frontValues[i + 1] = new double[0][];
            }
        }

        // obtain the maximum and minimum values of the Pareto front
        double[] maximumValues = getMaximumValues(union.writeObjectivesToMatrix(), numberOfObjectives);
        double[] minimumValues = getMinimumValues(union.writeObjectivesToMatrix(), numberOfObjectives);

        // normalized all the fronts
        double[][][] normalizedFront = new double[populations.length + 1][][];
        for (int i = 0; i < normalizedFront.length; i++) {
            if (frontValues[i].length > 0) {
                normalizedFront[i] = getNormalizedFront(frontValues[i], maximumValues, minimumValues);
            } else {
                normalizedFront[i] = new double[0][];
            }
        }

        // compute offsets for reference point in normalized space
        double[] offsets = new double[maximumValues.length];
        for (int i = 0; i < maximumValues.length; i++) {
            offsets[i] = offset_ / (maximumValues[i] - minimumValues[i]);
        }

	      //Inverse all the fronts front. This is needed because the original
        //metric by Zitzler is for maximization problems
        double[][][] invertedFront = new double[populations.length + 1][][];
        for (int i = 0; i < invertedFront.length; i++) {
            if (normalizedFront[i].length > 0) {
                invertedFront[i] = invertedFront(normalizedFront[i]);
            } else {
                invertedFront[i] = new double[0][];
            }
        }

        // shift away from origin, so that boundary points also get a contribution > 0
        for (double[][] anInvertedFront : invertedFront) {
            for (double[] point : anInvertedFront) {
                for (int i = 0; i < point.length; i++) {
                    point[i] += offsets[i];
                }
            }
        }

        // calculate contributions 
        double[] contribution = new double[populations.length];
        Hypervolume hypervolume = new Hypervolume();

        for (int i = 0; i < populations.length; i++) {
            if (invertedFront[i + 1].length == 0) {
                contribution[i] = 0;
            } else {
                if (invertedFront[i + 1].length != invertedFront[0].length) {
                    double[][] aux = new double[invertedFront[0].length - invertedFront[i + 1].length][];
                    int startPoint = 0, endPoint;
                    for (int j = 0; j < i; j++) {
                        startPoint += invertedFront[j + 1].length;
                    }
                    endPoint = startPoint + invertedFront[i + 1].length;
                    int index = 0;
                    for (int j = 0; j < invertedFront[0].length; j++) {
                        if (j < startPoint || j >= (endPoint)) {
                            aux[index++] = invertedFront[0][j];
                        }
                    }
	    		  	  //System.out.println(hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, getNumberOfObjectives));
                    //System.out.println(hypervolume.calculateHypervolume(aux, aux.length, getNumberOfObjectives));

                    contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives)
                            - hypervolume.calculateHypervolume(aux, aux.length, numberOfObjectives);
                } else {
                    contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives);
                }
            }
        }

	      //for (int i = 0; i < contribution.length; i++) 
        //System.out.println(invertedFront[0].length +" "+ invertedFront[i+1].length +" "+ contribution[i]);
        return contribution;
    }

    /**
     * Calculates the hv contribution of different populations. Receives an
     * array of populations and computes the contribution to HV of the
     * population consisting in the union of all of them
     *
     * @param populations, consisting in all the populatoins
     * @return HV contributions of each population
	   *
     */
    public double[] hvContributions(SolutionSet archive, SolutionSet[] populations) {

        SolutionSet union;
        int size = 0;
        double offset_ = 0.0;

        //determining the global size of the population
        for (SolutionSet population : populations) {
            size += population.size();
        }

        //allocating space for the union 
        union = archive;

        //determining the number of objectives		  
        int numberOfObjectives = union.get(0).getNumberOfObjectives();

        //writing everything in matrices
        double[][][] frontValues = new double[populations.length + 1][][];

        frontValues[0] = union.writeObjectivesToMatrix();
        for (int i = 0; i < populations.length; i++) {
            if (populations[i].size() > 0) {
                frontValues[i + 1] = populations[i].writeObjectivesToMatrix();
            } else {
                frontValues[i + 1] = new double[0][];
            }
        }

        // obtain the maximum and minimum values of the Pareto front
        double[] maximumValues = getMaximumValues(union.writeObjectivesToMatrix(), numberOfObjectives);
        double[] minimumValues = getMinimumValues(union.writeObjectivesToMatrix(), numberOfObjectives);

        // normalized all the fronts
        double[][][] normalizedFront = new double[populations.length + 1][][];
        for (int i = 0; i < normalizedFront.length; i++) {
            if (frontValues[i].length > 0) {
                normalizedFront[i] = getNormalizedFront(frontValues[i], maximumValues, minimumValues);
            } else {
                normalizedFront[i] = new double[0][];
            }
        }

        // compute offsets for reference point in normalized space
        double[] offsets = new double[maximumValues.length];
        for (int i = 0; i < maximumValues.length; i++) {
            offsets[i] = offset_ / (maximumValues[i] - minimumValues[i]);
        }

	      //Inverse all the fronts front. This is needed because the original
        //metric by Zitzler is for maximization problems
        double[][][] invertedFront = new double[populations.length + 1][][];
        for (int i = 0; i < invertedFront.length; i++) {
            if (normalizedFront[i].length > 0) {
                invertedFront[i] = invertedFront(normalizedFront[i]);
            } else {
                invertedFront[i] = new double[0][];
            }
        }

        // shift away from origin, so that boundary points also get a contribution > 0
        for (double[][] anInvertedFront : invertedFront) {
            for (double[] point : anInvertedFront) {
                for (int i = 0; i < point.length; i++) {
                    point[i] += offsets[i];
                }
            }
        }

        // calculate contributions 
        double[] contribution = new double[populations.length];
        Hypervolume hypervolume = new Hypervolume();

        for (int i = 0; i < populations.length; i++) {
            if (invertedFront[i + 1].length == 0) {
                contribution[i] = 0;
            } else {

                int auxSize = 0;
                for (int j = 0; j < populations.length; j++) {
                    if (j != i) {
                        auxSize += invertedFront[j + 1].length;
                    }
                }

                if (size == archive.size()) { // the contribution is the maximum hv
                    contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives);
                } else {
                    //make a front with all the populations but the target one
                    int index = 0;
                    double[][] aux = new double[auxSize][];
                    for (int j = 0; j < populations.length; j++) {
                        if (j != i) {
                            for (int k = 0; k < populations[j].size(); k++) {
                                aux[index++] = invertedFront[j + 1][k];
                            }
                        }
                    }
                    contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives)
                            - hypervolume.calculateHypervolume(aux, aux.length, numberOfObjectives);
                }

                /*
                 int size2 = 0;
                 for (int j = 0; j < populations.length; j++) 
                 size2+=invertedFront[j+1].length;
	    			  
	    			  
                 double [][] aux = new double[size2 - invertedFront[i+1].length][];
                 int index = 0;
                 for (int j = 0; j < populations.length; j++) {
                 if (j!=i) {						  
                 for (int k = 0; k < invertedFront[j+1].length; k++)
                 aux[index++] = invertedFront[j+1][k];
                 }
                 }
	    			  	    			  		  
                 System.out.println(hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, getNumberOfObjectives));
                 System.out.println(index+" "+aux.length);
                 System.out.println(hypervolume.calculateHypervolume(aux, aux.length, getNumberOfObjectives));
	    		  
    				  
    				  
                 contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, getNumberOfObjectives) -
                 hypervolume.calculateHypervolume(aux, aux.length, getNumberOfObjectives);
                 */
            }
        }

	      //for (int i = 0; i < contribution.length; i++) 
        //System.out.println(invertedFront[0].length +" "+ invertedFront[i+1].length +" "+ contribution[i]);
        return contribution;
    }

} // MetricsUtil
